package com.w3c.pragmatic.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;

/**
 * 日期工具类
 *
 * @author wangdong
 * @since 2025/3/4 18:11
 */
public class DateUtil {

  // format格式
  public static final String patten_ymd = "yyyyMMdd";
  public static final String patten_ymd_mid = "yyyy-MM-dd";
  public static final String patten_ymd_under = "yyyy_MM_dd";
  public static final String patten_ymd_hm = "yyyyMMddHHmm";
  public static final String patten_ymd_hm_mid = "yyyy-MM-dd HH:mm";
  public static final String patten_ymd_hms = "yyyyMMddHHmmss";
  public static final String patten_ymd_hms_mid = "yyyy-MM-dd HH:mm:ss";
  // 类型分类
  public static final String unit_seconds = "second";
  public static final String unit_minutes = "minute";
  public static final String unit_hours = "hour";
  public static final String unit_days = "day";
  public static final String unit_months = "month";

  /**
   * 获取当前的时间
   *
   * @return 返回当前的时间
   */
  public static Date getNowDate() {
    return new Date();
  }

  /**
   * 获得某天最后一秒日期00:00:00点时间
   *
   * @return 开始一秒
   */
  public static Date getDateStart(Date date) {
    // 方案一：使用hutool工具类 DateUtil.beginOfDay(date);
    // 返回2025-03-05 00:00:00; ISO 8601标准的简化日期时间格式, 不包含时区信息，默认是本地时间（CST） 【下同】
    // 方案二：jdk实现
    // 返回Wed Mar 05 00:00:00 CST 2025; 带有时区和星期信息的完整日期时间格式, “CST”是时区（中国标准时间，UTC+8） 【下同】
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.MILLISECOND, 0);
    return cal.getTime();
  }

  /**
   * 获得某天最后一秒日期23:59:59点时间
   *
   * @return 最后一秒
   */
  public static Date getDateEnd(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.HOUR_OF_DAY, 23);
    cal.set(Calendar.SECOND, 59);
    cal.set(Calendar.MINUTE, 59);
    cal.set(Calendar.MILLISECOND, 0);
    return cal.getTime();
  }

  /**
   * 把日期格式转化为字符串(默认yyyy-MM-dd HH:mm:ss)
   *
   * @param date 日期
   * @return 返回格式化之后的字符串
   */
  public static String dateToString(Date date) {
    return dateToString(date, patten_ymd_hms_mid);
  }

  /**
   * 把日期格式转化为字符串
   *
   * @param date 日期
   * @param format 格式
   * @return 返回格式化之后的字符串
   */
  public static String dateToString(Date date, String format) {
    SimpleDateFormat dateFormat = new SimpleDateFormat(format);
    return dateFormat.format(date);
  }

  /**
   * 把字符串转化为日期格式(默认yyyy-MM-dd HH:mm:ss)
   *
   * @param date 日期
   * @return 返回格式化之后的日期
   */
  public static Date stringToDate(String date) {
    return stringToDate(date, patten_ymd_hms_mid);
  }

  /**
   * 把字符串转化为日期格式
   *
   * @param date 日期
   * @param format 格式
   * @return 返回格式化之后的日期
   */
  public static Date stringToDate(String date, String format) {
    SimpleDateFormat dateFormat = new SimpleDateFormat(format);
    try {
      return dateFormat.parse(date);
    } catch (ParseException e) {
      return null;
    }
  }

  /**
   * 两个日期时间间隔(秒/分/时/天/月)
   *
   * @param form 开始时间
   * @param to 结束时间
   * @param differenceType 差值类型
   * @return 相差秒/分/时/天/月
   */
  public static int difference(Date form, Date to, String differenceType) {
    // 方案一：DateUtil.between(form, to, DateUnit.SECOND/DateUnit.MINUTE/DateUnit.HOUR/DAY), 这里返回的是绝对值
    // 方案二：根据类型枚举调用下面方法
    switch (differenceType) {
      case unit_seconds:
        return differenceSeconds(form, to);
      case unit_minutes:
        return differenceMinutes(form, to);
      case unit_hours:
        return differenceHours(form, to);
      case unit_months:
        return differenceMonths(form, to);
      default:
        return differenceDays(form, to);
    }
  }

  /**
   * 两个日期时间间隔(秒),不满1秒返回0
   *
   * @param form 开始时间
   * @param to 结束时间
   * @return 相差秒数
   */
  public static int differenceSeconds(Date form, Date to) {
    return (int) ((to.getTime() - form.getTime()) / (1000));
  }

  /**
   * 两个日期时间间隔(分钟),不满1分返回0
   *
   * @param form 开始时间
   * @param to 结束时间
   * @return 相差分钟数
   */
  public static int differenceMinutes(Date form, Date to) {
    return (int) ((to.getTime() - form.getTime()) / (1000 * 60));
  }

  /**
   * 两个日期时间间隔(小时),不满1时返回0
   *
   * @param form 开始时间
   * @param to 结束时间
   * @return 相差小时数
   */
  public static int differenceHours(Date form, Date to) {
    return (int) ((to.getTime() - form.getTime()) / (1000 * 60 * 60));
  }

  /**
   * 两个日期时间间隔(天),不满1天返回0
   *
   * @param form 开始时间
   * @param to 结束时间
   * @return 相差天数
   */
  public static int differenceDays(Date form, Date to) {
    // 方案一：使用hutool包的DateUtil.betweenDay(form, to, false);
    // 第三个参数false表示不重置时间那么隔天不满24小时则返回0；如果为true表示重置时间，即忽略后面是时分秒两天即使差1s也会返回1
    // 方案二：使用jdk时间计算(同上面的false)
    return (int) ((to.getTime() - form.getTime()) / (1000 * 60 * 60 * 24));
  }

  /**
   * 两个日期时间间隔(月),不满1月返回0
   *
   * @param form 开始时间
   * @param to 结束时间
   * @return 相差月数
   */
  public static int differenceMonths(Date form, Date to) {
    // 方案一：使用hutool包的DateUtil.betweenMonth(form, to, false);
    // 第三个参数false表示不重置时间那么隔月不满1月则返回0；如果为true，年差和月差之和忽略后面的数据
    // 方案二：使用jdk时间计算，年差和月差之和忽略后面的数据(同上面的true)
    Calendar bef = Calendar.getInstance();
    Calendar aft = Calendar.getInstance();
    bef.setTime(form);
    aft.setTime(to);
    int result = aft.get(Calendar.MONTH) - bef.get(Calendar.MONTH);
    int month = (aft.get(Calendar.YEAR) - bef.get(Calendar.YEAR)) * 12;
    return month + result;
  }

  /**
   * 通过传入的日期加/减指定的时间
   *
   * @param date 日期
   * @param offset 偏移数据
   * @param offsetType 偏移单位
   * @return 相加后的日期
   */
  public static Date getOffset(Date date, int offset, String offsetType) {
    // 方案一：DateUtil.offset(date,
    // DateField.SECOND/DateField.MINUTE/DateField.HOUR/DateField.DAY_OF_YEAR/MONTH, 10)
    // 方案二：根据类型枚举调用下面方法
    switch (offsetType) {
      case unit_seconds:
        return getOffsetSecond(date, offset);
      case unit_minutes:
        return getOffsetMinute(date, offset);
      case unit_hours:
        return getOffsetHour(date, offset);
      case unit_months:
        return getOffsetMonth(date, offset);
      default:
        return getOffsetDay(date, offset);
    }
  }

  /**
   * 通过传入的日期加/减指定的秒数
   *
   * @param date 日期
   * @param offsetSeconds 秒数
   * @return 相加后的日期
   */
  public static Date getOffsetSecond(Date date, int offsetSeconds) {
    // 方案一：使用hutool工具类 DateUtil.offsetSecond(date, 10);
    // 返回2025-03-05 00:00:10; ISO 8601标准的简化日期时间格式, 不包含时区信息，默认是本地时间（CST） 【下同】
    // 方案二：jdk实现
    // 返回Wed Mar 05 00:00:10 CST 2025; 带有时区和星期信息的完整日期时间格式, “CST”是时区（中国标准时间，UTC+8） 【下同】
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(Calendar.SECOND, offsetSeconds);
    return calendar.getTime();
  }

  /**
   * 通过传入的日期加/减指定的分数
   *
   * @param date 日期
   * @param offsetMinutes 分数
   * @return 相加后的日期
   */
  public static Date getOffsetMinute(Date date, int offsetMinutes) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(Calendar.MINUTE, offsetMinutes);
    return calendar.getTime();
  }

  /**
   * 通过传入的日期加/减指定的时数
   *
   * @param date 日期
   * @param offsetHours 时数
   * @return 相加后的日期
   */
  public static Date getOffsetHour(Date date, int offsetHours) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(Calendar.HOUR, offsetHours);
    return calendar.getTime();
  }

  /**
   * 通过传入的日期加/减指定的天数
   *
   * @param date 日期
   * @param offsetDays 天数
   * @return 相加后的日期
   */
  public static Date getOffsetDay(Date date, int offsetDays) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(Calendar.DAY_OF_YEAR, offsetDays);
    return calendar.getTime();
  }

  /**
   * 通过传入的日期加/减指定的月数
   *
   * @param date 日期
   * @param offsetMonths 月数
   * @return 相加后的日期
   */
  public static Date getOffsetMonth(Date date, int offsetMonths) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(Calendar.MONTH, offsetMonths);
    return calendar.getTime();
  }

  /**
   * 判断是否是同一时间
   *
   * @param date1 日期1
   * @param date2 日期2
   * @return 判断结果
   */
  public static boolean isSameTime(Date date1, Date date2) {
    // 方案一：使用hutool工具类 isSameTime(date1, date2)
    // 方案二：使用jdk
    return date1.compareTo(date2) == 0;
  }

  /**
   * 判断是否是同一天
   *
   * @param date1 日期1
   * @param date2 日期2
   * @return 判断结果
   */
  public static boolean isSameDay(Date date1, Date date2) {
    Calendar cal1 = Calendar.getInstance();
    cal1.setTime(date1);
    Calendar cal2 = Calendar.getInstance();
    cal2.setTime(date2);
    return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
        && cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH)
        && cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH);
  }

  /**
   * 判断是否是同一月
   *
   * @param date1 日期1
   * @param date2 日期2
   * @return 判断结果
   */
  public static boolean isSameMonth(Date date1, Date date2) {
    Calendar cal1 = Calendar.getInstance();
    cal1.setTime(date1);
    Calendar cal2 = Calendar.getInstance();
    cal2.setTime(date2);
    return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
        && cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH);
  }

  /**
   * 判断是否是同一年
   *
   * @param date1 日期1
   * @param date2 日期2
   * @return 判断结果
   */
  public static boolean isSameYear(Date date1, Date date2) {
    Calendar cal1 = Calendar.getInstance();
    cal1.setTime(date1);
    Calendar cal2 = Calendar.getInstance();
    cal2.setTime(date2);
    return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR);
  }

  /**
   * String转成LocalDateTime，ps:保证date和format一致（默认yyyy-MM-dd HH:mm:ss）
   *
   * @param date 参数时间
   * @return 返回String
   */
  public static LocalDateTime stringToLocalDateTime(String date) {
    // DateTimeFormatter
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(patten_ymd_hms_mid);
    return LocalDateTime.parse(date, dateTimeFormatter);
  }

  /**
   * String转成LocalDateTime，ps:保证date和format一致
   *
   * @param date 参数时间
   * @param formatter 格式
   * @return 返回String
   */
  public static LocalDateTime stringToLocalDateTime(String date, String formatter) {
    // DateTimeFormatter
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(formatter);
    return LocalDateTime.parse(date, dateTimeFormatter);
  }

  /**
   * LocalDateTime转成String(默认：yyyy-MM-dd HH:mm:ss)
   *
   * @param localDateTime 参数时间
   * @return 返回String
   */
  public static String localDateTimeToString(LocalDateTime localDateTime) {
    // 这里使用默认时区
    return localDateTimeToString(localDateTime, patten_ymd_hms_mid);
  }

  /**
   * LocalDateTime转成String
   *
   * @param localDateTime 参数时间
   * @param formatter 格式
   * @return 返回String
   */
  public static String localDateTimeToString(LocalDateTime localDateTime, String formatter) {
    // 这里使用默认时区
    return DateTimeFormatter.ofPattern(formatter).format(localDateTime);
  }

  /**
   * LocalDateTime转成Date
   *
   * @return 返回Date
   */
  public static Date localDateTimeToDate(LocalDateTime localDateTime) {
    // LocalDateTime不含时区的时间，所以要设置时区，这里默认用系统时区
    return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
  }

  /**
   * LocalDate转成Date
   *
   * @return 返回Date
   */
  public static Date localDateToDate(LocalDate localDate) {
    // LocalDate不含时区的时间，所以要设置时区，这里默认用系统时区
    return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
  }

  /**
   * LocalTime转成Date(由于LocalTime没有日期信息，这里必须有日期信息才能转化)
   *
   * @return 返回Date
   */
  public static Date localTimeToDate(LocalDate localDate, LocalTime localTime) {
    // LocalDate不含时区的时间，所以要设置时区，这里默认用系统时区
    return Date.from(
        LocalDateTime.of(localDate, localTime).atZone(ZoneId.systemDefault()).toInstant());
  }

  /**
   * Date转成LocalDateTime
   *
   * @return 返回LocalDateTime
   */
  public static LocalDateTime dateToLocalDateTime(Date date) {
    // LocalDateTime不含时区的时间，所以要设置时区，这里默认用系统时区
    return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
  }

  /**
   * Date转成LocalDate
   *
   * @return 返回LocalDate
   */
  public static LocalDate dateToLocalDate(Date date) {
    // LocalDate不含时区的时间，所以要设置时区，这里默认用系统时区
    return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
  }

  /**
   * Date转成LocalTime
   *
   * @return 返回LocalTime
   */
  public static LocalTime dateToLocalTime(Date date) {
    // LocalDate不含时区的时间，所以要设置时区，这里默认用系统时区
    return date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
  }

  /**
   * Date转成Timestamp
   *
   * @param date 日期
   * @return 时间戳
   */
  public static long dateToTimestamp(Date date) {
    return date.getTime();
  }

  /**
   * Timestamp转成Date
   *
   * @param timestamp 时间戳
   * @return 日期
   */
  public static Date timestampToDate(long timestamp) {
    return new Date(timestamp);
  }
}
